/* * ImageI/O-Ext - OpenSource Java Image translation Library * http://www.geo-solutions.it/ * http://java.net/projects/imageio-ext/ * (C) 2007 - 2009, GeoSolutions * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * either version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package it.geosolutions.imageio.utilities; import java.awt.Transparency; import java.awt.color.ColorSpace; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.ComponentColorModel; import java.awt.image.ComponentSampleModel; import java.awt.image.DataBuffer; import java.awt.image.DirectColorModel; import java.awt.image.IndexColorModel; import java.awt.image.MultiPixelPackedSampleModel; import java.awt.image.RenderedImage; import java.awt.image.SampleModel; import java.awt.image.SinglePixelPackedSampleModel; import java.awt.image.renderable.ParameterBlock; import java.awt.image.renderable.RenderedImageFactory; import java.io.File; import java.io.UnsupportedEncodingException; import java.net.URL; import java.net.URLDecoder; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Locale; import javax.imageio.ImageReader; import javax.imageio.spi.IIORegistry; import javax.imageio.spi.ImageReaderSpi; import javax.imageio.spi.ImageReaderWriterSpi; import javax.imageio.spi.ImageWriterSpi; import javax.imageio.spi.ServiceRegistry; import javax.imageio.stream.ImageInputStream; import javax.media.jai.JAI; import javax.media.jai.OperationRegistry; import javax.media.jai.PlanarImage; import javax.media.jai.ROI; import javax.media.jai.RenderedOp; import javax.media.jai.registry.RIFRegistry; import javax.media.jai.registry.RenderedRegistryMode; import javax.media.jai.widget.ScrollingImagePanel; import javax.swing.JFrame; import javax.swing.SwingUtilities; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import com.sun.imageio.plugins.common.BogusColorSpace; import com.sun.media.jai.codecimpl.util.RasterFactory; import com.sun.media.jai.operator.ImageReadDescriptor; /** * Simple class containing commonly used utility methods. */ @SuppressWarnings("deprecation") public class ImageIOUtilities { static final int MAX_SUBSAMPLING_FACTOR = Integer.MAX_VALUE; static final int MAX_LEVELS = 31; /** * An array of strings containing only white spaces. Strings' lengths are * equal to their index + 1 in the {@code spacesFactory} array. For example, * {@code spacesFactory[4]} contains a string of length 5. Strings are * constructed only when first needed. */ static final String[] spacesFactory = new String[20]; private ImageIOUtilities() { } private static final int DEFAULT_ROI = -999; /** * Creates a <code>ColorModel</code> that may be used with the * specified <code>SampleModel</code>. If a suitable * <code>ColorModel</code> cannot be found, this method returns * <code>null</code>. * * <p> Suitable <code>ColorModel</code>s are guaranteed to exist * for all instances of <code>ComponentSampleModel</code>. * For 1- and 3- banded <code>SampleModel</code>s, the returned * <code>ColorModel</code> will be opaque. For 2- and 4-banded * <code>SampleModel</code>s, the output will use alpha transparency * which is not premultiplied. 1- and 2-banded data will use a * grayscale <code>ColorSpace</code>, and 3- and 4-banded data a sRGB * <code>ColorSpace</code>. Data with 5 or more bands will have a * <code>BogusColorSpace</code>.</p> * * <p>An instance of <code>DirectColorModel</code> will be created for * instances of <code>SinglePixelPackedSampleModel</code> with no more * than 4 bands.</p> * * <p>An instance of <code>IndexColorModel</code> will be created for * instances of <code>MultiPixelPackedSampleModel</code>. The colormap * will be a grayscale ramp with <code>1 << numberOfBits</code> * entries ranging from zero to at most 255.</p> * * @return An instance of <code>ColorModel</code> that is suitable for * the supplied <code>SampleModel</code>, or <code>null</code>. * * @throws IllegalArgumentException If <code>sampleModel</code> is * <code>null</code>. */ public static final ColorModel createColorModel(SampleModel sampleModel) { // Check the parameter. if (sampleModel == null) { throw new IllegalArgumentException("sampleModel == null!"); } // Get the data type. int dataType = sampleModel.getDataType(); // Check the data type switch (dataType) { case DataBuffer.TYPE_BYTE: case DataBuffer.TYPE_USHORT: case DataBuffer.TYPE_SHORT: case DataBuffer.TYPE_INT: case DataBuffer.TYPE_FLOAT: case DataBuffer.TYPE_DOUBLE: break; default: // Return null for other types. return null; } // The return variable. ColorModel colorModel = null; // Get the sample size. int[] sampleSize = sampleModel.getSampleSize(); // Create a Component ColorModel. if (sampleModel instanceof ComponentSampleModel) { // Get the number of bands. int numBands = sampleModel.getNumBands(); // Determine the color space. ColorSpace colorSpace = null; if (numBands <= 2) { colorSpace = ColorSpace.getInstance(ColorSpace.CS_GRAY); } else if (numBands <= 4) { colorSpace = ColorSpace.getInstance(ColorSpace.CS_sRGB); } else { colorSpace = new BogusColorSpace(numBands); } boolean hasAlpha = (numBands == 2) || (numBands == 4); boolean isAlphaPremultiplied = false; int transparency = hasAlpha ? Transparency.TRANSLUCENT : Transparency.OPAQUE; if (dataType == DataBuffer.TYPE_SHORT) colorModel = new ComponentColorModel(colorSpace, sampleSize, hasAlpha, isAlphaPremultiplied, transparency, dataType); else colorModel = RasterFactory.createComponentColorModel(dataType, colorSpace, hasAlpha,isAlphaPremultiplied, transparency); } else if (sampleModel.getNumBands() <= 4 && sampleModel instanceof SinglePixelPackedSampleModel) { SinglePixelPackedSampleModel sppsm = (SinglePixelPackedSampleModel) sampleModel; int[] bitMasks = sppsm.getBitMasks(); int rmask = 0; int gmask = 0; int bmask = 0; int amask = 0; int numBands = bitMasks.length; if (numBands <= 2) { rmask = gmask = bmask = bitMasks[0]; if (numBands == 2) { amask = bitMasks[1]; } } else { rmask = bitMasks[0]; gmask = bitMasks[1]; bmask = bitMasks[2]; if (numBands == 4) { amask = bitMasks[3]; } } int bits = 0; for (int i = 0; i < sampleSize.length; i++) { bits += sampleSize[i]; } return new DirectColorModel(bits, rmask, gmask, bmask, amask); } else if (sampleModel instanceof MultiPixelPackedSampleModel) { // Load the colormap with a ramp. int bitsPerSample = sampleSize[0]; int numEntries = 1 << bitsPerSample; byte[] map = new byte[numEntries]; for (int i = 0; i < numEntries; i++) { map[i] = (byte) (i * 255 / (numEntries - 1)); } colorModel = new IndexColorModel(bitsPerSample, numEntries, map, map, map); } return colorModel; } /** * Given a root node, print the values/attributes tree using the System Out * * @TODO change it using Logger * @param root * the root node to be printed. */ public static void displayImageIOMetadata(Node root) { displayMetadata(root, 0); } static void indent(int level) { for (int i = 0; i < level; i++) { System.out.print(" "); } } static void displayMetadata(Node node, int level) { indent(level); // emit open tag System.out.print("<" + node.getNodeName()); NamedNodeMap map = node.getAttributes(); if (map != null) { // print attribute values int length = map.getLength(); for (int i = 0; i < length; i++) { Node attr = map.item(i); String attrValue = attr.getNodeValue(); if (attrValue == null) attrValue = ""; System.out.print(" " + attr.getNodeName() + "=\"" + attrValue + "\""); } } System.out.print(">"); // close current tag String nodeValue = node.getNodeValue(); if (nodeValue != null) System.out.println(" " + nodeValue); else System.out.println(""); Node child = node.getFirstChild(); if (child != null) { while (child != null) { // emit child tags recursively displayMetadata(child, level + 1); child = child.getNextSibling(); } indent(level); // emit close tag System.out.println("</" + node.getNodeName() + ">"); } else { // System.out.println("/>"); } } /** * Visualize the image, after rescaling its values, given a threshold for * the ROI. This is useful to rescale an image coming from a source which * may contain noDataValues. The ROI threshold allows to set a minimal value * to be included in the computation of the future JAI extrema operation * used before the JAI rescale operation. * * @param image * RenderedImage to visualize * @param title * title for the frame * @param roiThreshold * the threshold for the inner ROI */ static void visualizeRescaled(RenderedImage image, String title, int roiThreshold) { ROI roi = new ROI(image, roiThreshold); ParameterBlock pb = new ParameterBlock(); pb.addSource(image); // The source image if (roi != null) pb.add(roi); // The region of the image to scan // Perform the extrema operation on the source image RenderedOp op = JAI.create("extrema", pb); // Retrieve both the maximum and minimum pixel value double[][] extrema = (double[][]) op.getProperty("extrema"); final double[] scale = new double[] { (255) / (extrema[1][0] - extrema[0][0]) }; final double[] offset = new double[] { ((255) * extrema[0][0]) / (extrema[0][0] - extrema[1][0]) }; // Preparing to rescaling values ParameterBlock pbRescale = new ParameterBlock(); pbRescale.add(scale); pbRescale.add(offset); pbRescale.addSource(image); RenderedOp rescaledImage = JAI.create("Rescale", pbRescale); ParameterBlock pbConvert = new ParameterBlock(); pbConvert.addSource(rescaledImage); pbConvert.add(DataBuffer.TYPE_BYTE); RenderedOp destImage = JAI.create("format", pbConvert); visualize(destImage, title); } /** * base method used to simply visualize RenderedImage without further * information * * @param ri * RenderedImage to visualize */ public static void visualize(final RenderedImage ri) { visualize(ri, ""); } /** * base method used to simply visualize RenderedImage * * @param ri * RenderedImage to visualize * @param title * title for the frame (usually the image filename) */ public static void visualize(final RenderedImage ri, String title) { final JFrame frame = new JFrame(title); frame.getContentPane().add(new ScrollingImagePanel(ri, 1024, 768)); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); SwingUtilities.invokeLater(new Runnable() { public void run() { frame.pack(); frame.setSize(1024, 768); frame.setVisible(true); } }); } /** * base method used to visualize RenderedImage by specifying the frame title * as well as the request for rescaling. * * @param ri * RenderedImage to visualize * @param title * title for the frame (usually the image filename) * @param rescale * if <code>true</code> the RenderedImage will be rescaled * using a default value for the ROI. */ public static void visualize(final RenderedImage ri, String title, final boolean rescale) { visualize(ri, title, rescale, DEFAULT_ROI); } /** * base method used to visualize RenderedImage by specifying the frame title * as well as the request for rescaling. * * @param ri * RenderedImage to visualize * @param title * title for the frame (usually the image filename) * @param rescale * if <code>true</code> the RenderedImage will be rescaled * using a default value for the ROI. * @param roiThreshold * the threshold for the inner ROI. It represent the minimum * value of the pixels representing the region of interest. */ public static void visualize(RenderedImage ri, String title, boolean rescale, int roiThreshold) { if (rescale == true) visualizeRescaled(ri, title, roiThreshold); else visualize(ri, title); } // Method to return JDK core ImageReaderSPI/ImageWriterSPI for a // given formatName. public static List<ImageReaderWriterSpi> getJDKImageReaderWriterSPI(ServiceRegistry registry, String formatName, boolean isReader) { IIORegistry iioRegistry = (IIORegistry) registry; final Class<? extends ImageReaderWriterSpi> spiClass; final String descPart; if (isReader) { spiClass = ImageReaderSpi.class; descPart = " image reader"; } else { spiClass = ImageWriterSpi.class; descPart = " image writer"; } final Iterator<? extends ImageReaderWriterSpi> iter = iioRegistry.getServiceProviders(spiClass, true); // useOrdering String formatNames[]; ImageReaderWriterSpi provider; String desc = "standard " + formatName + descPart; String jiioPath = "com.sun.media.imageioimpl"; Locale locale = Locale.getDefault(); ArrayList<ImageReaderWriterSpi> list = new ArrayList<ImageReaderWriterSpi>(); while (iter.hasNext()) { provider = (ImageReaderWriterSpi) iter.next(); // Look for JDK core ImageWriterSpi's if (provider.getVendorName().startsWith("Sun Microsystems") && desc.equalsIgnoreCase(provider.getDescription(locale)) && // not JAI Image I/O plugins !provider.getPluginClassName().startsWith(jiioPath)) { // Get the formatNames supported by this Spi formatNames = provider.getFormatNames(); for (int i = 0; i < formatNames.length; i++) { if (formatNames[i].equalsIgnoreCase(formatName)) { // Must be a JDK provided ImageReader/ImageWriter list.add(provider); break; } } } } return list; } /** * Replace the original provider with name originalProviderName with the provider with name * customProviderName for the class providerClass and for the provided format . * @param providerClass the {@link Class} for the providers. * @param customProviderName the name of the provider we want to use as new preferred provider. * @param originalProviderName the name of the provider we want to deregister. * @param format the format for this provi * @return <code>true</code> if we find both of the providers and the replacement succeed, <code>false</code> otherwise. */ public static boolean replaceProvider( final Class<? extends ImageReaderWriterSpi> providerClass, final String customProviderName, final String originalProviderName, final String format){ // now we need to set the order final IIORegistry registry = IIORegistry.getDefaultInstance(); ImageReaderWriterSpi standard = null,custom = null; for (final Iterator<? extends ImageReaderWriterSpi> it = registry.getServiceProviders(providerClass, false); it.hasNext();) { final ImageReaderWriterSpi provider = it.next(); final String providerClassName=provider.getClass().getName(); final String[] formats = provider.getFormatNames(); for (int i=0; i<formats.length; i++) { if (formats[i].equalsIgnoreCase(format)) { if (providerClassName.equalsIgnoreCase(originalProviderName)) { standard = provider; } else if (providerClassName.equalsIgnoreCase(customProviderName)) { custom = provider; } if (standard!=null && custom!=null){ if(ImageReaderSpi.class.isAssignableFrom(standard.getClass())) return registry.setOrdering(ImageReaderSpi.class, (ImageReaderSpi)custom,(ImageReaderSpi) standard); else return registry.setOrdering(ImageWriterSpi.class, (ImageWriterSpi)custom,(ImageWriterSpi) standard); } } } } //we did not find them return false; } /** * Convenience method for testing two objects for equality. One or both * objects may be null. */ public static boolean equals(final Object object1, final Object object2) { return (object1 == object2) || (object1 != null && object1.equals(object2)); } /** * Returns {@code true} if the two specified objects implements exactly the * same set of interfaces. Only interfaces assignable to {@code base} are * compared. Declaration order doesn't matter. For example in ISO 19111, * different interfaces exist for different coordinate system geometries ({@code CartesianCS}, * {@code PolarCS}, etc.). */ public static boolean sameInterfaces(final Class<?> object1, final Class<?> object2, final Class<?> base) { if (object1 == object2) { return true; } if (object1 == null || object2 == null) { return false; } final Class<?>[] c1 = object1.getInterfaces(); final Class<?>[] c2 = object2.getInterfaces(); /* * Trim all interfaces that are not assignable to 'base' in the 'c2' * array. Doing this once will avoid to redo the same test many time in * the inner loops j=[0..n]. */ int n = 0; for (int i = 0; i < c2.length; i++) { final Class<?> c = c2[i]; if (base.isAssignableFrom(c)) { c2[n++] = c; } } /* * For each interface assignable to 'base' in the 'c1' array, check if * this interface exists also in the 'c2' array. Order doesn't matter. */ compare: for (int i = 0; i < c1.length; i++) { final Class<?> c = c1[i]; if (base.isAssignableFrom(c)) { for (int j = 0; j < n; j++) { if (c.equals(c2[j])) { System.arraycopy(c2, j + 1, c2, j, --n - j); continue compare; } } return false; // Interface not found in 'c2'. } } return n == 0; // If n>0, at least one interface was not found in 'c1'. } /** * Returns a string of the specified length filled with white spaces. This * method tries to return a pre-allocated string if possible. * * @param length * The string length. Negative values are clamped to 0. * @return A string of length {@code length} filled with white spaces. */ public static String spaces(int length) { // No need to synchronize. In the unlikely event of two threads // calling this method at the same time and the two calls creating a // new string, the String.intern() call will take care of // canonicalizing the strings. final int last = spacesFactory.length - 1; if (length < 0) length = 0; if (length <= last) { if (spacesFactory[length] == null) { if (spacesFactory[last] == null) { char[] blancs = new char[last]; Arrays.fill(blancs, ' '); spacesFactory[last] = new String(blancs).intern(); } spacesFactory[length] = spacesFactory[last] .substring(0, length).intern(); } return spacesFactory[length]; } else { char[] blancs = new char[length]; Arrays.fill(blancs, ' '); return new String(blancs); } } /** * Retrieves the class of the native type for the specified {@link DataBuffer} type. * * <p> * Spits an {@link IllegalArgumentException} in case the data type is unknown. * @param dataType {@link DataBuffer} type. * @return the class of the native type for the specified {@link DataBuffer} type or an {@link IllegalArgumentException}. */ public static Class<?> classForDataBufferType(final int dataType){ switch(dataType){ case DataBuffer.TYPE_BYTE: return Byte.class; case DataBuffer.TYPE_SHORT:case DataBuffer.TYPE_USHORT: return Short.class; case DataBuffer.TYPE_INT: return Integer.class; case DataBuffer.TYPE_FLOAT: return Float.class; case DataBuffer.TYPE_DOUBLE: return Double.class; case DataBuffer.TYPE_UNDEFINED: default: throw new IllegalArgumentException("Wrong datatype:"+dataType); } } /** * Returns a short class name for the specified class. This method will omit * the package name. For example, it will return "String" instead of * "java.lang.String" for a {@link String} object. It will also name array * according Java language usage, for example "double[]" instead of "[D". * * @param classe * The object class (may be {@code null}). * @return A short class name for the specified object. * * @todo Consider replacing by {@link Class#getSimpleName} when we will be * allowed to compile for J2SE 1.5. */ public static String getShortName(Class<?> classe) { if (classe == null) { return "<*>"; } int dimension = 0; Class<?> el; while ((el = classe.getComponentType()) != null) { classe = el; dimension++; } String name = classe.getName(); final int lower = name.lastIndexOf('.'); final int upper = name.length(); name = name.substring(lower + 1, upper).replace('$', '.'); if (dimension != 0) { StringBuffer buffer = new StringBuffer(name); do { buffer.append("[]"); } while (--dimension != 0); name = buffer.toString(); } return name; } /** * Takes a URL and converts it to a File. The attempts to deal with * Windows UNC format specific problems, specifically files located * on network shares and different drives. * * If the URL.getAuthority() returns null or is empty, then only the * url's path property is used to construct the file. Otherwise, the * authority is prefixed before the path. * * It is assumed that url.getProtocol returns "file". * * Authority is the drive or network share the file is located on. * Such as "C:", "E:", "\\fooServer" * * @param url a URL object that uses protocol "file" * @return a File that corresponds to the URL's location */ public static File urlToFile(URL url) { if (!"file".equals(url.getProtocol())) { return null; // not a File URL } String string = url.toExternalForm(); if (string.contains("+")) { // this represents an invalid URL created using either // file.toURL(); or // file.toURI().toURL() on a specific version of Java 5 on Mac string = string.replace("+", "%2B"); } try { string = URLDecoder.decode(string, "UTF-8"); } catch (UnsupportedEncodingException e) { throw new RuntimeException("Could not decode the URL to UTF-8 format", e); } String path3; String simplePrefix = "file:/"; String standardPrefix = "file://"; String os = System.getProperty("os.name"); if (os.toUpperCase().contains("WINDOWS") && string.startsWith(standardPrefix)) { // win32: host/share reference path3 = string.substring(standardPrefix.length() - 2); } else if (string.startsWith(standardPrefix)) { path3 = string.substring(standardPrefix.length()); } else if (string.startsWith(simplePrefix)) { path3 = string.substring(simplePrefix.length() - 1); } else { String auth = url.getAuthority(); String path2 = url.getPath().replace("%20", " "); if (auth != null && !auth.equals("")) { path3 = "//" + auth + path2; } else { path3 = path2; } } return new File(path3); } /** * Given a pair of xSubsamplingFactor (xSSF) and ySubsamplingFactor (ySFF), * look for a subsampling factor (SSF) in case xSSF != ySSF or they are not * powers of 2. * In case xSSF == ySSF == 2^N, the method return 0 (No optimal subsampling factor found). * * @param xSubsamplingFactor * @param ySubsamplingFactor * @return */ public static int getSubSamplingFactor2(final int xSubsamplingFactor, final int ySubsamplingFactor) { boolean resamplingIsRequired = false; int newSubSamplingFactor = 0; // Preliminar check: Are xSSF and ySSF different? final boolean subSamplingFactorsAreDifferent = (xSubsamplingFactor != ySubsamplingFactor); // Let be nSSF the minimum of xSSF and ySSF (They may be equals). newSubSamplingFactor = (xSubsamplingFactor <= ySubsamplingFactor) ? xSubsamplingFactor : ySubsamplingFactor; // if nSSF is greater than the maxSupportedSubSamplingFactor // (MaxSupSSF), it needs to be adjusted. final boolean changedSubSamplingFactors = (newSubSamplingFactor > MAX_SUBSAMPLING_FACTOR); if (newSubSamplingFactor > MAX_SUBSAMPLING_FACTOR) newSubSamplingFactor = MAX_SUBSAMPLING_FACTOR; final int optimalSubsampling = findOptimalSubSampling(newSubSamplingFactor); resamplingIsRequired = subSamplingFactorsAreDifferent || changedSubSamplingFactors || optimalSubsampling != newSubSamplingFactor; if (!resamplingIsRequired) { // xSSF and ySSF are equal and they are not greater than MaxSuppSSF newSubSamplingFactor = 0; } else { // xSSF and ySSF are different or they are greater than MaxSuppSFF. // We need to find a new subsampling factor to load a proper region. newSubSamplingFactor = optimalSubsampling; } return newSubSamplingFactor; } static int findOptimalSubSampling(final int newSubSamplingFactor) { int optimalSubSamplingFactor = 1; // finding the available subsampling factors from the number of // resolution levels for (int level = 0; level < MAX_LEVELS; level++) { // double the subSamplingFactor until it is lower than the // input subSamplingFactor if (optimalSubSamplingFactor < newSubSamplingFactor) optimalSubSamplingFactor = 1 << level; // if the calculated subSamplingFactor is greater than the input // subSamplingFactor, we need to step back by halving it. else if (optimalSubSamplingFactor > newSubSamplingFactor) { optimalSubSamplingFactor = optimalSubSamplingFactor >> 1; break; } else if (optimalSubSamplingFactor == newSubSamplingFactor) { break; } } return optimalSubSamplingFactor; } /** * Returns a short class name for the specified object. This method will * omit the package name. For example, it will return "String" instead of * "java.lang.String" for a {@link String} object. * * @param object * The object (may be {@code null}). * @return A short class name for the specified object. */ public static String getShortClassName(final Object object) { return getShortName(object != null ? object.getClass() : null); } public static String adjustAttributeName(final String attributeName){ if (attributeName.contains("\\")){ return attributeName.replace("\\", "_"); } return attributeName; } /** * Allows or disallow native acceleration for the specified operation on the given JAI instance. * By default, JAI uses hardware accelerated methods when available. For example, it make use of * MMX instructions on Intel processors. Unluckily, some native method crash the Java Virtual * Machine under some circumstances. For example on JAI 1.1.2, the {@code "Affine"} operation on * an image with float data type, bilinear interpolation and an {@link javax.media.jai.ImageLayout} * rendering hint cause an exception in medialib native code. Disabling the native acceleration * (i.e using the pure Java version) is a convenient workaround until Sun fix the bug. * <p> * <strong>Implementation note:</strong> the current implementation assumes that factories for * native implementations are declared in the {@code com.sun.media.jai.mlib} package, while * factories for pure java implementations are declared in the {@code com.sun.media.jai.opimage} * package. It work for Sun's 1.1.2 implementation, but may change in future versions. If this * method doesn't recognize the package, it does nothing. * * @param operation The operation name (e.g. {@code "Affine"}). * @param allowed {@code false} to disallow native acceleration. * @param jai The instance of {@link JAI} we are going to work on. This argument can be * omitted for the {@linkplain JAI#getDefaultInstance default JAI instance}. * * @see <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4906854">JAI bug report 4906854</a> */ public synchronized static void setNativeAccelerationAllowed(final String operation, final boolean allowed, final JAI jai) { final String product = "com.sun.media.jai"; final OperationRegistry registry = jai.getOperationRegistry(); // TODO: Check if we can remove SuppressWarnings with a future JAI version. @SuppressWarnings("unchecked") final List<RenderedImageFactory> factories = registry.getOrderedFactoryList( RenderedRegistryMode.MODE_NAME, operation, product); if (factories != null) { RenderedImageFactory javaFactory = null; RenderedImageFactory nativeFactory = null; Boolean currentState = null; for (final RenderedImageFactory factory : factories) { final String pack = factory.getClass().getPackage().getName(); if (pack.equals("com.sun.media.jai.mlib")) { nativeFactory = factory; if (javaFactory != null) { currentState = Boolean.FALSE; } } if (pack.equals("com.sun.media.jai.opimage")) { javaFactory = factory; if (nativeFactory != null) { currentState = Boolean.TRUE; } } } if (currentState!=null && currentState.booleanValue()!=allowed) { RIFRegistry.unsetPreference(registry, operation, product, allowed ? javaFactory : nativeFactory, allowed ? nativeFactory : javaFactory); RIFRegistry.setPreference(registry, operation, product, allowed ? nativeFactory : javaFactory, allowed ? javaFactory : nativeFactory); } } } /** * Allows or disallow native acceleration for the specified operation on the * {@linkplain JAI#getDefaultInstance default JAI instance}. This method is * a shortcut for <code>{@linkplain #setNativeAccelerationAllowed(String,boolean,JAI) * setNativeAccelerationAllowed}(operation, allowed, JAI.getDefaultInstance())</code>. * * @see #setNativeAccelerationAllowed(String, boolean, JAI) */ public static void setNativeAccelerationAllowed(final String operation, final boolean allowed) { setNativeAccelerationAllowed(operation, allowed, JAI.getDefaultInstance()); } public final static void checkNotNull (final Object checkMe, final String message){ if (checkMe == null){ throw new IllegalArgumentException(message != null ? message : "The provided object was NULL"); } } /** * Allow to dispose this image, as well as the related image sources. * * @param rOp * the image to be disposed. */ public static void disposeImage(RenderedImage rOp) { if (rOp != null) { if (rOp instanceof RenderedOp) { RenderedOp renderedOp = (RenderedOp) rOp; final int nSources = renderedOp.getNumSources(); if (nSources > 0) { for (int k = 0; k < nSources; k++) { Object source = null; try { source = renderedOp.getSourceObject(k); } catch (ArrayIndexOutOfBoundsException e) { // Ignore } if (source != null) { if (source instanceof RenderedOp) { disposeImage((RenderedOp) source); } else if (source instanceof BufferedImage) { ((BufferedImage) source).flush(); source = null; } } } } else { // get the reader Object imageReader = rOp.getProperty(ImageReadDescriptor.PROPERTY_NAME_IMAGE_READER); if (imageReader != null && imageReader instanceof ImageReader) { final ImageReader reader = (ImageReader) imageReader; final ImageInputStream stream = (ImageInputStream) reader.getInput(); try { stream.close(); } catch (Throwable e) { // swallow this } try { reader.dispose(); } catch (Throwable e) { // swallow this } } } final Object roi = rOp.getProperty("ROI"); if (roi != null && (roi instanceof ROI || roi instanceof RenderedImage)) { ROI roiImage = (ROI) roi; PlanarImage image = roiImage.getAsImage(); if (image != null) { image.dispose(); image = null; roiImage = null; } } if (rOp instanceof PlanarImage) { ((PlanarImage) rOp).dispose(); } else if (rOp instanceof BufferedImage) { ((BufferedImage) rOp).flush(); rOp = null; } } } } }